package nl.ru.rd.facedetection.nnbfd.neuralnetwork;

/**
 * A layer of neurons, with a specific size and specific activationFunction.
 * 
 * Each Neuron has a Weight. The activationfunction is shared by each Neuron.
 * 
 * @author Wouter Geraedts (s0814857)
 */
public class NLayer implements Layer
{
	private static final long serialVersionUID = -3203890582399273264L;
	private int size;
	private double[] activations;
	private double[][] weights;
	private Activationfunction f;

	private Layer previousLayer;
	private Layer nextLayer = null;

	/**
	 * Creates a layer of neurons, with a specific size and specific activationFunction.
	 * 
	 * @param previousLayer
	 *            The previous layer in the hierarchy of this specific Network, used in forwardPropagation (calculation of the activation of each Neuron).
	 * @param size
	 *            The size of this layer, equals the number of Neurons in this layer.
	 * @param f
	 *            The specific activationfunction to be used by each Neuron in this layer.
	 */
	public NLayer(Layer previousLayer, int size, Activationfunction f)
	{
		previousLayer.bind(this);

		this.size = size;
		this.previousLayer = previousLayer;
		this.f = f;
		this.activations = new double[size];

		this.weights = new double[size][previousLayer.getSize()];
		for(int i = 0; i < size; i++)
			for(int j = 0; j < previousLayer.getSize(); j++)
				this.weights[i][j] = Math.random() * 2.0 - 1.0;
	}

	/**
	 * Gets the size of this layer.
	 * 
	 * In this specific case the number of neurons in this layer.
	 * 
	 * @see nl.ru.rd.facedetection.nnbfd.neuralnetwork.Layer#getSize()
	 */
	public int getSize()
	{
		return this.size;
	}

	/**
	 * Get the activation for an element in this layer.
	 * 
	 * In this specific case the activation of the specified Neuron.
	 * 
	 * @param i
	 *            The index of the neuron in this layer.
	 * 
	 * @see nl.ru.rd.facedetection.nnbfd.neuralnetwork.Layer#getActivation(int)
	 */
	public double getActivation(int i)
	{
		return this.activations[i];
	}

	/**
	 * Gets the weight for a specific synapse.
	 * 
	 * @param i
	 *            The index of the neuron in this layer.
	 * @param j
	 *            The index of the neuron in the previousLayer.
	 * @return The weight of a specific synapse.
	 * 
	 * @see NLayer#getWeight(int, int)
	 */
	protected double getWeight(int i, int j)
	{
		return this.weights[i][j];
	}

	/**
	 * Sets the weight for a specific synapse.
	 * 
	 * @param i
	 *            The index of the neuron in this layer.
	 * @param j
	 *            The index of the neuron in the previousLayer.
	 * @param value
	 *            The new weight for the specific synapse.
	 * 
	 * @see NLayer#setWeight(int, int, double)
	 */
	protected void setWeight(int i, int j, double value)
	{
		this.weights[i][j] = value;
	}

	/**
	 * Gets the activationfunction used in this layer.
	 * 
	 * @return The activationfunction used in this layer.
	 */
	public Activationfunction getActivationfunction()
	{
		return this.f;
	}

	/**
	 * Updates the activations of this layer.
	 * 
	 * @see nl.ru.rd.facedetection.nnbfd.neuralnetwork.Layer#update()
	 */
	public void update()
	{
		for(int i = 0; i < this.activations.length; i++)
		{
			double sum = 0.0;
			for(int j = 0; j < this.previousLayer.getSize(); j++)
				sum += this.weights[i][j] * this.previousLayer.getActivation(j);

			this.activations[i] = this.f.calculate(sum);
		}
	}

	/**
	 * Binds this layer to another layer, the another layer being the next layer in the hiarchy of this network.
	 * 
	 * Should be called by the constructor of the next layer.
	 * 
	 * @param layer
	 *            The next layer in the hierarchy of this network.
	 * @see nl.ru.rd.facedetection.nnbfd.neuralnetwork.Layer#bind(nl.ru.rd.facedetection.nnbfd.neuralnetwork.Layer)
	 */
	public void bind(Layer layer)
	{
		this.nextLayer = layer;
	}

	/**
	 * Gets the previous layer in the sequence of layers.
	 * 
	 * @return The previous layer in the sequence of layers.
	 */
	public Layer getPreviousLayer()
	{
		return this.previousLayer;
	}

	public Layer getNextLayer()
	{
		return this.nextLayer;
	}
}